package org.wheel.plugins.starter.jdbc.pool.util;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.ast.VariableReference;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.wheel.plugins.starter.jdbc.pool.bean.XmlSqlMeta;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 纯 DOM + SpEL 实现，统一返回 SqlMeta
 */
public class LightXmlSqlBuilder {

    private static final ExpressionParser SPEL = new SpelExpressionParser();

    /* ================ 入口：直接传 XML 文本 ================ */
    public static XmlSqlMeta build(String xmlSql, Map<String, Object> param) throws Exception {
        Document doc = toDocument(xmlSql);

        StandardEvaluationContext ctx = new StandardEvaluationContext();
        ctx.setVariables(param);

        StringBuilder sql = new StringBuilder();
        List<Map<String, Object>> args  = new ArrayList<>();
        List<Map<String, Object>> replaceArgs = new ArrayList<>();
        traverse(doc.getDocumentElement(), ctx, sql, args, replaceArgs);
        return new XmlSqlMeta(sql.toString().trim(), args, replaceArgs);
    }

    private static Document toDocument(String raw) throws Exception {
        // 先去掉首尾空白
        raw = raw.strip();
        if (!raw.startsWith("<")) {
            raw = "<sql>\n" + raw + "\n</sql>";
        }
        return DocumentBuilderFactory.newInstance()
                .newDocumentBuilder()
                .parse(new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8)));
    }

    /* ---------- 递归 ---------- */
    private static void traverse(Node node, StandardEvaluationContext ctx,
                                 StringBuilder sql, List<Map<String, Object>> args, List<Map<String, Object>> replaceArgs) {

        if (node.getNodeType() == Node.TEXT_NODE) {
            sql.append(node.getTextContent());
            return;
        }
        String tag = node.getNodeName();
        switch (tag) {
            case "where"  -> handleWhere(node, ctx, sql, args, replaceArgs);
            case "if"     -> handleIf(node, ctx, sql, args, replaceArgs);
            case "foreach"-> handleForeach(node, ctx, sql, args, replaceArgs);
            default -> {   // 其它标签直接透传子节点
                NodeList list = node.getChildNodes();
                for (int i = 0; i < list.getLength(); i++) {
                    traverse(list.item(i), ctx, sql, args, replaceArgs);
                }
            }
        }
    }

    private static void handleWhere(Node where, StandardEvaluationContext ctx,
                                    StringBuilder sql, List<Map<String, Object>> args, List<Map<String, Object>> replaceArgs) {
        int lenBefore = sql.length();
        NodeList list = where.getChildNodes();
        for (int i = 0; i < list.getLength(); i++) {
            traverse(list.item(i), ctx, sql, args, replaceArgs);
        }
        if (sql.length() > lenBefore) {
            String body = sql.substring(lenBefore).trim();
            sql.setLength(lenBefore);
            sql.append(" WHERE ").append(body.replaceFirst("^(AND|OR)\\s+", ""));
        }
    }

    private static void handleIf(Node ifNode, StandardEvaluationContext ctx,
                                 StringBuilder sql, List<Map<String, Object>> args, List<Map<String, Object>> replaceArgs) {
        String test = ((Element) ifNode).getAttribute("test");
        Boolean ok = SPEL.parseExpression(test).getValue(ctx, Boolean.class);
        if (Boolean.TRUE.equals(ok)) {
            NodeList list = ifNode.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                traverse(list.item(i), ctx, sql, args, replaceArgs);
            }
        }
    }

    private static void handleForeach(Node foreach, StandardEvaluationContext ctx,
                                      StringBuilder sql, List<Map<String, Object>> args, List<Map<String, Object>> replaceArgs) {
        Element el = (Element) foreach;
        String collExpr = el.getAttribute("collection"); // #{statusList}
        String item     = el.getAttribute("item");
        String open     = el.getAttribute("open");
        String close    = el.getAttribute("close");
        String sep      = el.getAttribute("separator");

        Object coll = SPEL.parseExpression(collExpr).getValue(ctx);
        if (!(coll instanceof Iterable)) {
            return;
        }
//        String varMapKey = collectVars(collExpr).iterator().next();
//        assert coll != null;
//        replaceArgs.add(Map.of(varMapKey, coll));
//        Iterable<?> it = (Iterable<?>) coll;
//        StringBuilder sub = new StringBuilder();
//        int idx = 0;
//        for (Object o : it) {
//            if (idx++ > 0 && !sep.isEmpty()) {
//                sub.append(sep);
//            }
//            String key = item+idx;
//            String piece = el.getTextContent().replaceAll("#\\{" + item + "\\}", "#\\{" + key + "\\}");
//            sub.append(piece.trim());
//            args.add(Map.of(key, o));              // 顺序收集参数
//        }

        // 原样输出
        StringBuilder sub = new StringBuilder(el.getTextContent());

        sql.append(open).append(sub).append(close);
    }

    /* ---------- 获取变量 ---------- */
    private static Set<String> collectVars(String spel) {
        SpelExpression expr = (SpelExpression) SPEL.parseExpression(spel);
        Set<String> vars = new HashSet<>();
        astWalk(expr.getAST(), vars);
        return vars;
    }

    private static void astWalk(SpelNode node, Set<String> vars) {
        if (node instanceof VariableReference) {
            vars.add(node.toStringAST().replaceAll("#" , ""));
        }
        for (int i = 0; i < node.getChildCount(); i++) {
            astWalk(node.getChild(i), vars);
        }
    }

}
